(ns jna.win.pdh
  (:use jna
        jna.win
        jna.win.error
        jna.win.pdhmsg
        jna.win.perf)
  (:require clojure.string)
  (:import [com.sun.jna WString Pointer]))

(def PDH_FMT_LONG 0x00000100)
(def PDH_STATUS Integer)

(def-jna-struct PDH_FMT_COUNTERVALUE
  [:DWORD :CStatus]
  [(jna-union
    [:LONG     :longValue]
    [:double   :doubleValue]
    [:LONGLONG :largeValue]
    [:LPCSTR   :AnsiStringValue]
    [:LPCWSTR  :WideStringValue]) :union])


(defjna PDH_STATUS pdh PdhCollectQueryData)
(defjna PDH_STATUS pdh PdhOpenQueryW)
(defjna PDH_STATUS pdh PdhCloseQuery)
(defjna PDH_STATUS pdh PdhAddCounterW)
(defjna PDH_STATUS pdh PdhGetFormattedCounterValue)
(defjna PDH_STATUS pdh PdhEnumObjectItemsW)
(defjna PDH_STATUS pdh PdhEnumObjectsW)


(defwin pdh-collect-query-data PdhCollectQueryData)
(defwin pdh-open-query PdhOpenQueryW)
(def pdh-close-query PdhCloseQuery)
(defwin pdh-add-counterw PdhAddCounterW)
(defwin pdh-get-formatted-counter-value PdhGetFormattedCounterValue)

(defn- pdh-enum-object-items
  ([object
    pwsCounterListBuffer dwCounterListSize
    pwsInstanceListBuffer dwInstanceListSize
    expected-code]
    (let [status (PdhEnumObjectItemsW NULL
                                      NULL
                                      (WString. object)
                                      (if (= pwsCounterListBuffer NULL) NULL (address-of pwsCounterListBuffer))
                                      (byref dwCounterListSize)
                                      (if (= pwsInstanceListBuffer NULL) NULL (address-of pwsInstanceListBuffer))
                                      (byref dwInstanceListSize)
                                      PERF_DETAIL_WIZARD
                                      0)]
      (when (not= status expected-code)
        (throw
          (RuntimeException.
            (format "Windows Error: %s returns %d (0x%x|0x%x)"
              'PdhEnumObjectItemsW status status (bit-and 0xFFFFFFFF status)))))
      status))
  ([object
    pwsCounterListBuffer dwCounterListSize
    pwsInstanceListBuffer dwInstanceListSize]
    (pdh-enum-object-items object pwsCounterListBuffer dwCounterListSize pwsInstanceListBuffer dwInstanceListSize ERROR_SUCCESS))
  ([object dwCounterListSize dwInstanceListSize]
    (pdh-enum-object-items object NULL dwCounterListSize NULL dwInstanceListSize PDH_MORE_DATA)))

(defn- pdh-enum-objects
  ([buffer size expected-code]
    (let [status (PdhEnumObjectsW
                  NULL
                  NULL
                  (if (= buffer NULL) NULL (address-of buffer))
                  (byref size)
                  PERF_DETAIL_WIZARD
                  false)]
        (when (not= status expected-code)
          (throw
            (RuntimeException.
              (format "Windows Error: %s returns %d (0x%x|0x%x)"
                'PdhEnumObjectsW status status (bit-and 0xFFFFFFFF status)))))
      status))
  ([size]
    (pdh-enum-objects NULL size PDH_MORE_DATA))
  ([buffer size]
    (pdh-enum-objects buffer size ERROR_SUCCESS)))

;;; API

(declare pdh-collect)
(declare pdh-get-value)

;; query
(def ^:dynamic *known-pdh-queries* nil)

(defn pdh-add-counter [query]
  (let [hQuery (jna-new :HANDLE)
        hCounter (jna-new :HANDLE)]
    (pdh-open-query NULL 0 (byref hQuery))
    (pdh-add-counterw (byvalue hQuery) (WString. query) 0 (byref hCounter))
    (let [q {:query hQuery
             :counter hCounter}]
      (pdh-collect q)
      q)))

(defn pdh-remove-counter [{hQuery :query}]
  (pdh-close-query (byvalue hQuery)))

(defn ^:dynamic pdh-register [q]
  (let [qq (pdh-add-counter q)]
    (when *known-pdh-queries*
      (swap! *known-pdh-queries* conj qq))
    qq))

(defmacro pdh-with-query-cache [& body]
  `(binding [pdh-register (memoize pdh-register)
             *known-pdh-queries* (atom [])]
    ~@body))

(defn pdh-query [q]
  (pdh-get-value q))

(defn pdh-query-once [q]
  (let [q (pdh-add-counter q)]
    (try
      (pdh-query q)
      (finally
        (pdh-remove-counter q)))))

(defn pdh-collect [{hQuery :query}]
  (pdh-collect-query-data (byvalue hQuery)))

(defn pdh-collect-all
  ([qs]
    (doseq [q qs]
      (pdh-collect q))
    (when *known-pdh-queries*
      (doseq [q @*known-pdh-queries*]
        (pdh-collect q))))
  ([]
    (pdh-collect-all nil)))

(defn pdh-unregister-all
  ([qs]
    (doseq [q qs]
      (pdh-remove-counter q))
    (when *known-pdh-queries*
      (swap! *known-pdh-queries*
        (fn [qs]
          (doseq [q qs] (pdh-remove-counter q))
          nil))))
  ([]
    (pdh-unregister-all nil)))


;; read value

(defn- pdh-get-value-from [v]
  (let [status (jna-struct-get :CStatus v)]
    (when-not (= status PDH_CSTATUS_VALID_DATA)
      (throw (Exception. (format "Windows Error: %s returns %d (0x%x|0x%x)" 'PdhGetFormattedCounterValue status status (bit-and 0xFFFFFFFF status))))))
  (jna-union-get :longValue (jna-struct-get :union v)))

(defn pdh-get-value [{hCounter :counter :as q}]
  (let [v (jna-struct-new PDH_FMT_COUNTERVALUE)]
    (let [status (PdhGetFormattedCounterValue (byvalue hCounter) PDH_FMT_LONG NULL (byref v))]
      (if (= status ERROR_SUCCESS)
        (pdh-get-value-from v)
        (do
          (pdh-collect q)
          (pdh-get-formatted-counter-value (byvalue hCounter) PDH_FMT_LONG NULL (byref v))
          (pdh-get-value-from v))))))

(defn pdh-get-long [q]
  (pdh-get-value q))

;; list

(defn- parse-enum-buffer [buffer]
  (let [z (clojure.string/join "" (jna-array-seq buffer))
        z (.substring z 0 (- (.length z) 2))]
    (clojure.string/split z #"\x00")))

(defn pdh-objects []
  (let [size (jna-new :DWORD)
        status (pdh-enum-objects size)
        buffer (jna-array-new :WCHAR (jna-def size))
        status (pdh-enum-objects buffer size)]
    (parse-enum-buffer buffer)))

(defn pdh-query-object [object]
  (let [pwsCounterListBuffer (jna-new :LPWSTR)
        dwCounterListSize (jna-new :DWORD)
        pwsInstanceListBuffer (jna-new :LPWSTR)
        dwInstanceListSize (jna-new :DWORD)
        status (pdh-enum-object-items object dwCounterListSize dwInstanceListSize)
        pwsCounterListBuffer (jna-array-new :WCHAR (jna-def dwCounterListSize))
        pwsInstanceListBuffer (jna-array-new :WCHAR (jna-def dwInstanceListSize))
        status (pdh-enum-object-items object pwsCounterListBuffer dwCounterListSize pwsInstanceListBuffer dwInstanceListSize)]
    (let [z (clojure.string/join "" (jna-array-seq pwsCounterListBuffer))
          z (.substring z 0 (- (.length z) 2))
          counterList (clojure.string/split z #"\x00")
          z (clojure.string/join "" (jna-array-seq pwsInstanceListBuffer))
          z (.substring z 0 (- (.length z) 2))
          instanceList (clojure.string/split z #"\x00")]
      [instanceList counterList])))


(comment

(println (pdh-query-object "Processor"))

(let [q (pdh-add-counter "\\Process(firefox)\\Working Set")]
  (println (pdh-get-long q)))

(let [q (pdh-add-counter "\\Processor(_Total)\\% Processor Time")]
  (dotimes [i 60]
    (Thread/sleep 1000)
    (println (pdh-get-long q))
    (pdh-collect q)))

(println (size-of PDH_FMT_COUNTERVALUE))
)

